package org.geometerplus.zlibrary.text.view;

import android.util.Pair;

import com.example.administrator.fangbook.dataLibrary.book.IBookbase;
import com.example.administrator.fangbook.read.ReadActivity;
import com.example.administrator.fangbook.read.bean.Chapter;

import org.geometerplus.fbreader.book.M17kPlainTxtBook;
import org.geometerplus.fbreader.bookmodel.TOCTree;
import org.geometerplus.fbreader.fbreader.FBReaderApp;
import org.geometerplus.zlibrary.core.application.ZLApplication;
import org.geometerplus.zlibrary.core.filesystem.ZLFile;
import org.geometerplus.zlibrary.core.util.ZLColor;
import org.geometerplus.zlibrary.core.view.SelectionCursor;
import org.geometerplus.zlibrary.core.view.ZLPaintContext;
import org.geometerplus.zlibrary.core.view.ZLViewEnums;
import org.geometerplus.zlibrary.text.hyphenation.ZLTextHyphenationInfo;
import org.geometerplus.zlibrary.text.hyphenation.ZLTextHyphenator;
import org.geometerplus.zlibrary.text.model.ZLTextAlignmentType;
import org.geometerplus.zlibrary.text.model.ZLTextMark;
import org.geometerplus.zlibrary.text.model.ZLTextModel;
import org.geometerplus.zlibrary.text.model.ZLTextParagraph;
import org.geometerplus.zlibrary.ui.android.view.animation.AnimationManager;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

/**
 * 页面数据存储类
 */
public class ModelData {

    /**
     * FBReader的application
     */
    private ZLApplication Application;
    /**
     *
     */
    private ZLTextView myView;

//    protected ZLTextModel myModel;

    protected ZLTextPage myPreviousPage = new ZLTextPage();
    protected ZLTextPage myCurrentPage = new ZLTextPage();
    protected ZLTextPage myNextPage = new ZLTextPage();

    public ModelData(ZLTextView textView) {
        myView = textView;
        Application = myView.Application;
    }


    public synchronized void paintInSameModel(ZLPaintContext context, ZLViewEnums.PageIndex pageIndex) {
//        if (myModel == null || myModel.getParagraphsNumber() == 0) {
//            return;
//        }
        ZLTextPage page;
        switch (pageIndex) {
            default:
            case current:
                page = myCurrentPage;
                break;
            case previous:
                page = myPreviousPage;
//                //上一页的页面状态是没有准备好的
//                if (myPreviousPage.PaintState == PaintStateEnum.NOTHING_TO_PAINT) {
//                    preparePaintInfo(myCurrentPage);
//                    myPreviousPage.EndCursor.setCursor(myCurrentPage.StartCursor);
//                    myPreviousPage.PaintState = PaintStateEnum.END_IS_KNOWN;
//                }
                break;
            case next:
                page = myNextPage;
//                if (myNextPage.PaintState == PaintStateEnum.NOTHING_TO_PAINT) {
//                    preparePaintInfo(myCurrentPage);
//                    myNextPage.StartCursor.setCursor(myCurrentPage.EndCursor);
//                    myNextPage.PaintState = PaintStateEnum.START_IS_KNOWN;
//                }
                break;
        }
        page.TextElementMap.clear();
        preparePaintInfo(page);
        paintPage(context, page);
    }

    public synchronized void preparePaintInfo(ZLTextPage page) {
        page.setSize(myView.getTextColumnWidth(), myView.getTextAreaHeight(), myView.twoColumnView(), page == myPreviousPage);
        if (page.PaintState == PaintStateEnum.NOTHING_TO_PAINT || page.PaintState == PaintStateEnum.READY) {
            return;
        }
        final int oldState = page.PaintState;
        final HashMap<ZLTextLineInfo, ZLTextLineInfo> cache = myLineInfoCache;
        for (ZLTextLineInfo info : page.LineInfos) {
            cache.put(info, info);
        }
        switch (page.PaintState) {
            default:
                break;
            case PaintStateEnum.TO_SCROLL_FORWARD:
                if (!page.EndCursor.isEndOfText()) {
                    final ZLTextWordCursor startCursor = new ZLTextWordCursor();
                    switch (myScrollingMode) {
                        case ZLTextView.ScrollingMode.NO_OVERLAPPING:
                            break;
                        case ZLTextView.ScrollingMode.KEEP_LINES:
                            page.findLineFromEnd(startCursor, myOverlappingValue);
                            break;
                        case ZLTextView.ScrollingMode.SCROLL_LINES:
                            page.findLineFromStart(startCursor, myOverlappingValue);
                            if (startCursor.isEndOfParagraph()) {
                                startCursor.nextParagraph();
                            }
                            break;
                        case ZLTextView.ScrollingMode.SCROLL_PERCENTAGE:
                            page.findPercentFromStart(startCursor, myOverlappingValue);
                            break;
                    }

                    if (!startCursor.isNull() && startCursor.samePositionAs(page.StartCursor)) {
                        page.findLineFromStart(startCursor, 1);
                    }

                    if (!startCursor.isNull()) {
                        final ZLTextWordCursor endCursor = new ZLTextWordCursor();
                        buildInfos(page, startCursor, endCursor);
                        if (!page.isEmptyPage() && (myScrollingMode != ZLTextView.ScrollingMode.KEEP_LINES || !endCursor.samePositionAs(page.EndCursor))) {
                            page.StartCursor.setCursor(startCursor);
                            page.EndCursor.setCursor(endCursor);
                            break;
                        }
                    }

                    page.StartCursor.setCursor(page.EndCursor);
                    buildInfos(page, page.StartCursor, page.EndCursor);
                }
                break;
            case PaintStateEnum.TO_SCROLL_BACKWARD:
                scrollToBackward(page);
                break;
            case PaintStateEnum.START_IS_KNOWN:
                if (!page.StartCursor.isNull()) {
                    buildInfos(page, page.StartCursor, page.EndCursor);
                }
                break;
            case PaintStateEnum.END_IS_KNOWN:
                if (!page.EndCursor.isNull()) {
                    page.StartCursor.setCursor(findStartOfPrevousPage(page, page.EndCursor));
                    buildInfos(page, page.StartCursor, page.EndCursor);
                }
                break;
        }
        page.PaintState = PaintStateEnum.READY;

        myLineInfoCache.clear();

        if (page == myCurrentPage) {
            if (oldState != PaintStateEnum.START_IS_KNOWN) {
                myPreviousPage.reset();
            }
            if (oldState != PaintStateEnum.END_IS_KNOWN) {
                myNextPage.reset();
            }
        }
    }


    private boolean endPager = false;

    /**
     * 当是最后一页的时候不切换页面标识符，直接跳转阅读最后页
     *
     * @param endPager
     */
    public void setisEndPager(boolean endPager) {
        this.endPager = endPager;
    }

    private interface SizeUnit {
        int PIXEL_UNIT = 0;
        int LINE_UNIT = 1;
    }

    public synchronized boolean isLoaded() {
        return myModel != null && myModel.isLoaded();
    }

    protected final HashMap<ZLTextLineInfo, ZLTextLineInfo> myLineInfoCache = new HashMap<ZLTextLineInfo, ZLTextLineInfo>();

    private ZLTextRegion.Soul myOutlinedRegionSoul;
    private boolean myShowOutline = true;

    protected ZLTextSelection mySelection = null;
    private final Set<ZLTextHighlighting> myHighlightings =
            Collections.synchronizedSet(new TreeSet<ZLTextHighlighting>());

    private CursorManager myCursorManager;

    //add by Loy
    private boolean myHighlightSelectedRegion = true;
    private ZLTextRegion.Soul mySelectedRegionSoul;
    //protected ZLTextSelection myEditSelection;
    //end by Loy


    private TOCTree mReadingTOCTree;
    private static final char[] SPACE = new char[]{' '};
    protected ReadActivity.EpubDirectory myEpubDirData;
    protected ZLFile myLoadFile;

    public TOCTree.Reference getReadingTOCTree() {
        return mReadingTOCTree != null ? mReadingTOCTree.getDumpReference()
                : null;
    }


    public String getChapterTitle(ZLViewEnums.PageIndex index) {
        FBReaderApp myReader = (FBReaderApp) FBReaderApp.Instance();
        if (myReader == null) {
            return "";
        }
        if (myReader.Model == null) {
            return "";
        }
        if (myReader.Model.Book == null) {
            return "";
        }
        if (myReader.Model.Book.File == null) {
            return "";
        }
        if (myReader.Model.Book.File.getShelfBook() == null) {
            return "";
        }
        if (myReader.Model.Book.File.getShelfBook().getBookType() == IBookbase.BookType.Type_ChineseAll
                || myReader.Model.Book.File.getShelfBook().getBookType() == IBookbase.BookType.Type_Txt ||
                myReader.Model.Book.File.getShelfBook().getBookType() == IBookbase.BookType.Type_Migu) {
            Object chapter = myReadinChapter;
            if (chapter == null || myReader == null || myReader.Model == null || myReader.Model.Book == null) {
                return "";
            }
            if (chapter instanceof Chapter) {
                return ((Chapter) chapter).getName();
            }
        }

        String title = "";
        if (myReader.Model.Book.File.getShelfBook().getBookType() == IBookbase.BookType.Type_Epub) {
            final TOCTree root = myReader.Model.TOCTree;
            int startParagraphIndex = getStartCursor(index)
                    .getParagraphIndex();
            if (myEpubDirData == null
                    || myEpubDirData.isValided(myReader.Model.Book.File
                    .getShelfBook())) {
                List<TOCTree> tress = myView.getEpubDirData(root,
                        myReader.Model.Book.File.getShelfBook()).getItems();
                for (TOCTree tree : tress) {

                    if (tree.getReference() == null) {
                        continue;
                    }

                    if ((startParagraphIndex + 1) >= tree.getReference().ParagraphIndex) {
                        title = tree.getText();
                        mReadingTOCTree = tree;
                    }
                }
                if (mReadingTOCTree == null) {
                    mReadingTOCTree = myReader.Model.TOCTree;
                }
            }
            return title;

        }
        return "";
    }

    private int myScrollingMode;
    protected int myOverlappingValue;

    protected Object myReadinChapter;

    /**
     * 绑定章节和native层的解析结构 bindChapterModel
     *
     * @param readingChapter
     * @param model
     */
    public synchronized void setModel(Object readingChapter, ZLTextModel model) {
        setModel(readingChapter, model, null);
    }

    /**
     * 翻到第一页
     */
    public void gotoFirstPage() {
        if (!isStartOfText()) {//不是第一页跳转到第一页
            gotoPosition(0, 0, 0);
        }
    }

    /**
     * 绑定章节和native层的解析结构 bindChapterModel
     *
     * @param readingChapter
     * @param model
     * @param position
     */
    public synchronized void setModel(Object readingChapter, ZLTextModel model, ZLTextPosition position) {
        myReadinChapter = readingChapter;
        myLineInfoCache.clear();
        myCursorManager = model != null ? new CursorManager(model, myView.getExtensionManager()) : null;
        mySelection = null;

        myHighlightings.clear();
        myModel = model;
        myCurrentPage.reset();
        myCurrentPage.setModel(myModel);
        myPreviousPage.reset();
        myPreviousPage.setModel(myModel);
        myNextPage.reset();
        myNextPage.setModel(myModel);
        load();
        LogUtil.i(GlobalConstants.READER_LOG, "ModelData  setModel() load() 重新设置myModel");
        if (myModel != null) {
            final int paragraphsNumber = myModel.getParagraphsNumber();
            if (paragraphsNumber > 0) {
                myCurrentPage.moveStartCursor(myCursorManager.get(0));
            }
        }
        if (position != null) {//段落，行 ，第几字符
            myCurrentPage.moveStartCursor(position.getParagraphIndex(), position.getElementIndex(), position.getCharIndex());
        }
    }

    private int mTotalElementSize = 0;

    public synchronized void load() {
        if (myModel != null) {
            final int paragraphsNumber = myModel.getParagraphsNumber();
            int id = 0;
            mTotalElementSize = 0;
            ZLTextParagraphCursor cursor = paragraphsNumber > 0 ? myCursorManager.get(0) : null;
            while (cursor != null) {
                mTotalElementSize += cursor.getParagraphLength();
                cursor = cursor.next();
            }
        }

    }

    public final ZLTextModel getModel() {
        return myModel;
    }

    public final Object getReadingChapter() {
        return myReadinChapter;
    }

    public synchronized void gotoLastPage() {
        if (!isLoaded()) {
            return;
        }
        if (!isEndOfText()) {
            getEndCursor().moveToParagraph(getModel().getParagraphsNumber() - 1);
            getEndCursor().moveToParagraphEnd();
            gotoPositionByEnd(getEndCursor().getParagraphIndex(), getEndCursor().getElementIndex(),
                    getEndCursor().getCharIndex());
        }
    }

    public synchronized boolean isStartOfText() {
        return getStartCursor().isStartOfText();
    }

    public boolean isEndOfText() {
        return getEndCursor().isEndOfText();
    }

    public ZLTextWordCursor getStartCursor() {
        if (myCurrentPage.StartCursor.isNull()) {
            preparePaintInfo(myCurrentPage);
        }
        return myCurrentPage.StartCursor;
    }

    public ZLTextWordCursor getEndCursor() {
        if (myCurrentPage.EndCursor.isNull()) {
            preparePaintInfo(myCurrentPage);
        }
        return myCurrentPage.EndCursor;
    }

    public synchronized void gotoMark(ZLTextMark mark) {
        if (mark == null) {
            return;
        }

        myPreviousPage.reset();
        myNextPage.reset();
        boolean doRepaint = false;
        if (myCurrentPage.StartCursor.isNull()) {
            doRepaint = true;
            preparePaintInfo(myCurrentPage);
        }
        if (myCurrentPage.StartCursor.isNull()) {
            return;
        }
        if (myCurrentPage.StartCursor.getParagraphIndex() != mark.ParagraphIndex ||
                myCurrentPage.StartCursor.getMark().compareTo(mark) > 0) {
            doRepaint = true;
            gotoPosition(mark.ParagraphIndex, 0, 0);
            preparePaintInfo(myCurrentPage);
        }
        if (myCurrentPage.EndCursor.isNull()) {
            preparePaintInfo(myCurrentPage);
        }
        while (mark.compareTo(myCurrentPage.EndCursor.getMark()) > 0) {
            doRepaint = true;
            turnPage(true, ZLTextView.ScrollingMode.NO_OVERLAPPING, 0);
            preparePaintInfo(myCurrentPage);
        }
        if (doRepaint) {
            if (myCurrentPage.StartCursor.isNull()) {
                preparePaintInfo(myCurrentPage);
            }
            Application.getViewWidget().repaintForcely();
        }
    }

    public synchronized void gotoHighlighting(ZLTextHighlighting highlighting) {
        myPreviousPage.reset();
        myNextPage.reset();
        boolean doRepaint = false;
        if (myCurrentPage.StartCursor.isNull()) {
            doRepaint = true;
            preparePaintInfo(myCurrentPage);
        }
        if (myCurrentPage.StartCursor.isNull()) {
            return;
        }
        if (!highlighting.intersects(myCurrentPage)) {
            gotoPosition(highlighting.getStartPosition().getParagraphIndex(), 0, 0);
            preparePaintInfo(myCurrentPage);
        }
        if (myCurrentPage.EndCursor.isNull()) {
            preparePaintInfo(myCurrentPage);
        }
        while (!highlighting.intersects(myCurrentPage)) {
            doRepaint = true;
            turnPage(true, ZLTextView.ScrollingMode.NO_OVERLAPPING, 0);
            preparePaintInfo(myCurrentPage);
        }
        if (doRepaint) {
            if (myCurrentPage.StartCursor.isNull()) {
                preparePaintInfo(myCurrentPage);
            }
            Application.getViewWidget().repaintForcely();
        }
    }

    public synchronized int search(final String text, boolean ignoreCase, boolean wholeText, boolean backward, boolean thisSectionOnly) {
        if (myModel == null || text.length() == 0) {
            return 0;
        }
        int startIndex = 0;
        int endIndex = myModel.getParagraphsNumber();
        if (thisSectionOnly) {

        }
        int count = myModel.search(text, startIndex, endIndex, ignoreCase);
        myPreviousPage.reset();
        myNextPage.reset();
        if (!myCurrentPage.StartCursor.isNull()) {
            rebuildPaintInfo();
            if (count > 0) {
                ZLTextMark mark = myCurrentPage.StartCursor.getMark();
                gotoMark(wholeText ?
                        (backward ? myModel.getLastMark() : myModel.getFirstMark()) :
                        (backward ? myModel.getPreviousMark(mark) : myModel.getNextMark(mark)));
            }
            Application.getViewWidget().repaintForcely();
        }
        return count;
    }

    public boolean canFindNext() {
        final ZLTextWordCursor end = myCurrentPage.EndCursor;
        return !end.isNull() && (myModel != null) && (myModel.getNextMark(end.getMark()) != null);
    }

    public synchronized void findNext() {
        final ZLTextWordCursor end = myCurrentPage.EndCursor;
        if (!end.isNull()) {
            gotoMark(myModel.getNextMark(end.getMark()));
        }
    }

    public boolean canFindPrevious() {
        final ZLTextWordCursor start = myCurrentPage.StartCursor;
        return !start.isNull() && (myModel != null) && (myModel.getPreviousMark(start.getMark()) != null);
    }

    public synchronized void findPrevious() {
        final ZLTextWordCursor start = myCurrentPage.StartCursor;
        if (!start.isNull()) {
            gotoMark(myModel.getPreviousMark(start.getMark()));
        }
    }

    public void clearFindResults() {
        if (!findResultsAreEmpty()) {
            myModel.removeAllMarks();
            rebuildPaintInfo();
            Application.getViewWidget().repaintForcely();
        }
    }

    public boolean findResultsAreEmpty() {
        return myModel == null || myModel.getMarks().isEmpty();
    }


    public synchronized void onScrollingFinished(ZLViewEnums.PageIndex pageIndex) {
        final FBReaderApp reader = (FBReaderApp) FBReaderApp.Instance();
        if (reader == null) {
            return;
        }

        switch (pageIndex) {
            case current:
                break;
            case previous: {
                final ZLTextPage swap = myNextPage;
                myNextPage = myCurrentPage;
                myCurrentPage = myPreviousPage;
                myPreviousPage = swap;
                myPreviousPage.reset();
                if (myCurrentPage.PaintState == PaintStateEnum.NOTHING_TO_PAINT) {
                    preparePaintInfo(myNextPage);
                    myCurrentPage.EndCursor.setCursor(myNextPage.StartCursor);
                    myCurrentPage.PaintState = PaintStateEnum.END_IS_KNOWN;
                } else if (!myCurrentPage.EndCursor.isNull() &&
                        !myNextPage.StartCursor.isNull() &&
                        !myCurrentPage.EndCursor.samePositionAs(myNextPage.StartCursor)) {
                    myNextPage.reset();
                    myNextPage.StartCursor.setCursor(myCurrentPage.EndCursor);
                    myNextPage.PaintState = PaintStateEnum.START_IS_KNOWN;
                    Application.getViewWidget().reset();
                }
                break;
            }
            case next: {
                if (endPager) {
                    endPager = false;
                    break;
                }
                final ZLTextPage swap = myPreviousPage;
                myPreviousPage = myCurrentPage;
                myCurrentPage = myNextPage;
                myNextPage = swap;
                myNextPage.reset();
                switch (myCurrentPage.PaintState) {
                    case PaintStateEnum.NOTHING_TO_PAINT:
                        preparePaintInfo(myPreviousPage);
                        myCurrentPage.StartCursor.setCursor(myPreviousPage.EndCursor);
                        myCurrentPage.PaintState = PaintStateEnum.START_IS_KNOWN;
                        break;
                    case PaintStateEnum.READY:
                        myNextPage.StartCursor.setCursor(myCurrentPage.EndCursor);
                        myNextPage.PaintState = PaintStateEnum.START_IS_KNOWN;
                        break;
                }
                break;
            }
        }
    }

    public boolean removeHighlightings(Class<? extends ZLTextHighlighting> type) {
        boolean result = false;
        synchronized (myHighlightings) {
            for (Iterator<ZLTextHighlighting> it = myHighlightings.iterator(); it.hasNext(); ) {
                final ZLTextHighlighting h = it.next();
                if (type.isInstance(h)) {
                    it.remove();
                    result = true;
                }
            }
        }
        return result;
    }

    public void adddHighligthings(ZLTextHighlighting h) {
        myHighlightings.add(h);
    }

    public final void addHighlightings(Collection<ZLTextHighlighting> hilites) {
        myHighlightings.addAll(hilites);
    }

    protected void moveSelectionCursorTo(SelectionCursor.Which which, int x, int y) {
        if (mySelection == null) {
            return;
        }
        y -= myView.getTextStyleCollection().getBaseStyle().getFontSize() / 2;
        mySelection.setCursorInMovement(which, x, y);
        mySelection.expandTo(myCurrentPage, x, y);

    }

    public void clearHighlighting() {
        if (removeHighlightings(ZLTextManualHighlighting.class)) {
            Application.getViewWidget().repaintForcely();
        }
    }


    protected void releaseSelectionCursor() {
        if (mySelection == null) {
            return;
        }
        mySelection.stop();

    }

    protected SelectionCursor.Which getSelectionCursorInMovement() {
        return mySelection != null ? mySelection.getCursorInMovement() : null;
    }

    protected ZLTextSelection.Point getSelectionCursorPoint(ZLTextPage page, SelectionCursor.Which which) {
        return NotesData.instance().getSelectionCursorPoint(mySelection, page, which);
    }

    private float distance2ToCursor(int x, int y, SelectionCursor.Which which) {
        final ZLTextSelection.Point point = getSelectionCursorPoint(myCurrentPage, which);
        if (point == null) {
            return Float.MAX_VALUE;
        }
        final float dX = x - point.X;
        final float dY = y - point.Y;
        return dX * dX + dY * dY;
    }

    protected SelectionCursor.Which findSelectionCursor(int x, int y, float maxDistance2) {
        if (mySelection == null || mySelection.isEmpty()) {
            return null;
        }

        final float leftDistance2 = distance2ToCursor(x, y, SelectionCursor.Which.Left);
        final float rightDistance2 = distance2ToCursor(x, y, SelectionCursor.Which.Right);

        if (rightDistance2 < leftDistance2) {
            return rightDistance2 <= maxDistance2 ? SelectionCursor.Which.Right : null;
        } else {
            return leftDistance2 <= maxDistance2 ? SelectionCursor.Which.Left : null;
        }
    }

    public ZLTextPage getPage(ZLViewEnums.PageIndex pageIndex) {
        switch (pageIndex) {
            default:
            case current:
                return myCurrentPage;
            case previous:
                return myPreviousPage;
            case next:
                return myNextPage;
        }
    }

    protected synchronized void paintPage(ZLPaintContext context, ZLTextPage page) {
        LogUtil.e("LOY", "paintPage page hashcode:" + page.hashCode());
        LogUtil.i(GlobalConstants.READER_LOG, "ModelData paintPage  布局当前页面的内容 " + page.LineInfos.size());
        if (page.StartCursor.isNull() || page.EndCursor.isNull()) {
            return;
        }

        final ArrayList<ZLTextLineInfo> lineInfos = page.LineInfos;
        final int[] labels = new int[lineInfos.size() + 1];
        int x = myView.getLeftMargin();
        int y = myView.getTopMargin();
        if (page.StartCursor.isStartOfText()) {
            y += AndroidUtils.dip2px(GlobalApp.getInstance(), 50);
        }
        int index = 0;
        int columnIndex = 0;
        ZLTextLineInfo previousInfo = null;
        for (ZLTextLineInfo info : lineInfos) {
            info.adjust(previousInfo);
            prepareTextLine(page, info, x, y, columnIndex);
            y += info.Height + info.Descent + info.VSpaceAfter;
            labels[++index] = page.TextElementMap.size();

            if (index == page.Column0Height) {
                y = myView.getTopMargin();
                x += page.getTextWidth() + myView.getSpaceBetweenColumns();
                columnIndex = 1;
            }
            previousInfo = info;
        }

        final List<ZLTextHighlighting> hilites = findHilites(page);

        x = myView.getLeftMargin();
        y = myView.getTopMargin();
        if (page.StartCursor.isStartOfText()) {
            y += AndroidUtils.dip2px(GlobalApp.getInstance(), 50);
        }
        index = 0;
        for (ZLTextLineInfo info : lineInfos) {
            drawTextLine(page, hilites, info, labels[index], labels[index + 1], y);
            y += info.Height + info.Descent + info.VSpaceAfter;
            ++index;
            if (index == page.Column0Height) {
                y = myView.getTopMargin();
                x += page.getTextWidth() + myView.getSpaceBetweenColumns();
            }
        }

//        for (ZLTextHighlighting h : hilites) {
//            int mode = Hull.DrawMode.None;
//
//            final ZLColor bgColor = h.getBackgroundColor();
//            if (bgColor != null) {
//                context.setFillColor(bgColor, 128);
//                mode |= Hull.DrawMode.Fill;
//            }
//
//            final ZLColor outlineColor = h.getOutlineColor();
//            if (outlineColor != null) {
//                context.setLineColor(outlineColor);
//                mode |= Hull.DrawMode.Outline;
//            }
//
//            if (mode != Hull.DrawMode.None) {
//                h.hull(page).draw(getContext(), mode);
//            }
//        }
//
//        final ZLTextRegion outlinedElementRegion = getOutlinedRegion(page);
//        if (outlinedElementRegion != null && myShowOutline) {
//            context.setLineColor(getSelectionBackgroundColor());
//            outlinedElementRegion.hull().draw(context, Hull.DrawMode.Outline);
//        }

        myView.drawSelectionCursor(context, page, SelectionCursor.Which.Left);
        myView.drawSelectionCursor(context, page, SelectionCursor.Which.Right);

        ZLTextSelection.Point leftPoint = null;
        ZLTextSelection.Point rightPoint = null;
        NotesData.instance().removeSelection(getEditSelection());
        FBReaderApp myReader = (FBReaderApp) FBReaderApp.Instance();
        if (myReader != null && myReader.Model != null && myReader.Model.Book != null && myReader.Model.Book instanceof M17kPlainTxtBook) {
            M17kPlainTxtBook book = (M17kPlainTxtBook) myReader.Model.Book;
            Chapter readingChapter = (Chapter) myReadinChapter;
//                    book.getReader().getReadingChapter();
            //Log.d("Note", "note chapterId:" + readingChapter + " readingChapterId:" + readingChapter != null ? ("" +  readingChapter.getId()) : "readingChapter == null");
            if (readingChapter != null && readingChapter.getId() != null) {
                NotesData.instance().drawNoteFlag(context, page, readingChapter);
            }
        }
    }

    protected final synchronized int sizeOfTextBeforeParagraph(int paragraphIndex) {
        return myModel != null ? myModel.getTextLength(paragraphIndex - 1) : 0;
    }

    protected final synchronized int sizeOfFullText() {
        if (myModel == null || myModel.getParagraphsNumber() == 0) {
            return 1;
        }
        return myModel.getTextLength(myModel.getParagraphsNumber() - 1);
    }

    public final synchronized int getCurrentCharNumber(ZLViewEnums.PageIndex pageIndex, boolean startNotEndOfPage) {


        if (myModel == null || myModel.getParagraphsNumber() == 0) {
            return 0;
        }
        final ZLTextPage page = getPage(pageIndex);
        preparePaintInfo(page);
        if (startNotEndOfPage) {
            return Math.max(0, sizeOfTextBeforeCursor(page.StartCursor));
        } else {
            int end = sizeOfTextBeforeCursor(page.EndCursor);
            if (end == -1) {
                end = myModel.getTextLength(myModel.getParagraphsNumber() - 1) - 1;
            }
            return Math.max(1, end);
        }
    }

    public ZLTextWordCursor getStartCursor(ZLViewEnums.PageIndex index) {
        ZLTextPage zPage = null;
        if (index == null || index == ZLViewEnums.PageIndex.current) {
            zPage = myCurrentPage;
        } else if (index == ZLViewEnums.PageIndex.next) {
            zPage = myNextPage;
        } else if (index == ZLViewEnums.PageIndex.previous) {
            zPage = myPreviousPage;
        }
        if (zPage != null) {
            if (zPage.StartCursor.isNull()) {
                preparePaintInfo(zPage);
            }
            return zPage.StartCursor;
        }
        return null;
    }

    public ZLTextWordCursor getEndCursor(ZLViewEnums.PageIndex index) {
        ZLTextPage zPage = null;
        if (index == null || index == ZLViewEnums.PageIndex.current) {
            zPage = myCurrentPage;
        } else if (index == ZLViewEnums.PageIndex.next) {
            zPage = myNextPage;
        } else if (index == ZLViewEnums.PageIndex.previous) {
            zPage = myPreviousPage;
        }
        if (zPage != null) {
            if (zPage.EndCursor.isNull()) {
                preparePaintInfo(zPage);
            }
            return zPage.EndCursor;
        }
        return null;
    }

    private int sizeOfTextBeforeCursor(ZLTextWordCursor wordCursor) {
        final ZLTextParagraphCursor paragraphCursor = wordCursor.getParagraphCursor();
        if (paragraphCursor == null) {
            return -1;
        }
        final int paragraphIndex = paragraphCursor.Index;
        int sizeOfText = myModel.getTextLength(paragraphIndex - 1);
        final int paragraphLength = paragraphCursor.getParagraphLength();
        if (paragraphLength > 0) {
            sizeOfText +=
                    (myModel.getTextLength(paragraphIndex) - sizeOfText)
                            * wordCursor.getElementIndex()
                            / paragraphLength;
        }
        return sizeOfText;
    }

    // Can be called only when (myModel.getParagraphsNumber() != 0)
    private synchronized float computeCharsPerPage() {
        myView.setTextStyle(myView.getTextStyleCollection().getBaseStyle());

        final int textWidth = myView.getTextColumnWidth();
        final int textHeight = myView.getTextAreaHeight();

        final int num = myModel.getParagraphsNumber();
        final int totalTextSize = myModel.getTextLength(num - 1);
        final float charsPerParagraph = ((float) totalTextSize) / num;

        final float charWidth = computeCharWidth();

        final int indentWidth = myView.getElementWidth(ZLTextElement.Indent, 0);
        final float effectiveWidth = textWidth - (indentWidth + 0.5f * textWidth) / charsPerParagraph;
        float charsPerLine = Math.min(effectiveWidth / charWidth,
                charsPerParagraph * 1.2f);

        final int strHeight = myView.getWordHeight() + myView.getContext().getDescent();
        final int effectiveHeight = (int)
                (textHeight -
                        (myView.getTextStyle().getSpaceBefore(myView.metrics())
                                + myView.getTextStyle().getSpaceAfter(myView.metrics()) / 2) / charsPerParagraph);
        final int linesPerPage = effectiveHeight / strHeight;

        return charsPerLine * linesPerPage;
    }

    private synchronized int computeTextPageNumber(int textSize) {
        if (myModel == null || myModel.getParagraphsNumber() == 0) {
            return 1;
        }

        final float factor = 1.0f / computeCharsPerPage();
        final float pages = textSize * factor;
        return Math.max((int) (pages + 1.0f - 0.5f * factor), 1);
    }

    public final char[] myLettersBuffer = new char[512];
    private int myLettersBufferLength = 0;
    private ZLTextModel myLettersModel = null;
    protected float myCharWidth = -1f;
    private static final char[] ourDefaultLetters = "System developers have used modeling languages for decades to specify, visualize, construct, and document systems. The Unified Modeling Language (UML) is one of those languages. UML makes it possible for team members to collaborate by providing a common language that applies to a multitude of different systems. Essentially, it enables you to communicate solutions in a consistent, tool-supported language.".toCharArray();

    private final float computeCharWidth() {
        if (myLettersModel != myModel) {
            myLettersModel = myModel;
            myLettersBufferLength = 0;
            myCharWidth = -1f;

            int paragraph = 0;
            final int textSize = myModel.getTextLength(myModel.getParagraphsNumber() - 1);
            if (textSize > myLettersBuffer.length) {
                paragraph = myModel.findParagraphByTextLength((textSize - myLettersBuffer.length) / 2);
            }
            while (paragraph < myModel.getParagraphsNumber()
                    && myLettersBufferLength < myLettersBuffer.length) {
                final ZLTextParagraph.EntryIterator it = myModel.getParagraph(paragraph++).iterator();
                try {
                    while (myLettersBufferLength < myLettersBuffer.length && it.next()) {
                        if (it.getType() == ZLTextParagraph.Entry.TEXT) {
                            final int len = Math.min(it.getTextLength(),
                                    myLettersBuffer.length - myLettersBufferLength);
                            System.arraycopy(it.getTextData(), it.getTextOffset(),
                                    myLettersBuffer, myLettersBufferLength, len);
                            myLettersBufferLength += len;
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            if (myLettersBufferLength == 0) {
                myLettersBufferLength = Math.min(myLettersBuffer.length, ourDefaultLetters.length);
                System.arraycopy(ourDefaultLetters, 0, myLettersBuffer, 0, myLettersBufferLength);
            }
        }

        if (myCharWidth < 0f) {
            myCharWidth = computeCharWidth(myLettersBuffer, myLettersBufferLength);
        }
        return myCharWidth;
    }

    private final float computeCharWidth(char[] pattern, int length) {
        return myView.getContext().getStringWidth(pattern, 0, length) / ((float) length);
    }

    protected ZLTextSelection.Point getSelectionCursorPoint(ZLTextSelection section, ZLTextPage page,
                                                            SelectionCursor.Which cursor) {
        if (section == null) {
            return null;
        }

        if (cursor == section.getCursorInMovement()) {
            return section.getCursorInMovementPoint();
        }

        if (cursor == SelectionCursor.Which.Left) {
            if (section.hasPartBeforePage(page)) {
                return null;
            }
            final ZLTextElementArea selectionStartArea = section
                    .getStartArea(page);
            if (selectionStartArea != null) {
                return new ZLTextSelection.Point(selectionStartArea.XStart,
                        selectionStartArea.YEnd);
            }
        } else {
            if (section.hasPartAfterPage(page)) {
                return null;
            }
            final ZLTextElementArea selectionEndArea = section
                    .getEndArea(page);
            if (selectionEndArea != null) {
                return new ZLTextSelection.Point(selectionEndArea.XEnd,
                        selectionEndArea.YEnd);
            }
        }
        return null;
    }


    public final synchronized ZLTextView.PagePosition pagePosition() {
        int current = computeTextPageNumber(getCurrentCharNumber(ZLViewEnums.PageIndex.current, false));
        int total = computeTextPageNumber(sizeOfFullText());

        if (total > 3) {
            return new ZLTextView.PagePosition(current, total);
        }

        preparePaintInfo(myCurrentPage);
        ZLTextWordCursor cursor = myCurrentPage.StartCursor;
        if (cursor == null || cursor.isNull()) {
            return new ZLTextView.PagePosition(current, total);
        }

        if (cursor.isStartOfText()) {
            current = 1;
        } else {
            ZLTextWordCursor prevCursor = myPreviousPage.StartCursor;
            if (prevCursor == null || prevCursor.isNull()) {
                preparePaintInfo(myPreviousPage);
                prevCursor = myPreviousPage.StartCursor;
            }
            if (prevCursor != null && !prevCursor.isNull()) {
                current = prevCursor.isStartOfText() ? 2 : 3;
            }
        }

        total = current;
        cursor = myCurrentPage.EndCursor;
        if (cursor == null || cursor.isNull()) {
            return new ZLTextView.PagePosition(current, total);
        }
        if (!cursor.isEndOfText()) {
            ZLTextWordCursor nextCursor = myNextPage.EndCursor;
            if (nextCursor == null || nextCursor.isNull()) {
                preparePaintInfo(myNextPage);
                nextCursor = myNextPage.EndCursor;
            }
            if (nextCursor != null) {
                total += nextCursor.isEndOfText() ? 1 : 2;
            }
        }

        return new ZLTextView.PagePosition(current, total);
    }


    public final synchronized void gotoPage(int page) {
        if (myModel == null || myModel.getParagraphsNumber() == 0) {
            return;
        }

        final float factor = computeCharsPerPage();
        final float textSize = page * factor;

        int intTextSize = (int) textSize;
        int paragraphIndex = myModel.findParagraphByTextLength(intTextSize);

        if (paragraphIndex > 0 && myModel.getTextLength(paragraphIndex) > intTextSize) {
            --paragraphIndex;
        }
        intTextSize = myModel.getTextLength(paragraphIndex);

        int sizeOfTextBefore = myModel.getTextLength(paragraphIndex - 1);
        while (paragraphIndex > 0 && intTextSize == sizeOfTextBefore) {
            --paragraphIndex;
            intTextSize = sizeOfTextBefore;
            sizeOfTextBefore = myModel.getTextLength(paragraphIndex - 1);
        }

        final int paragraphLength = intTextSize - sizeOfTextBefore;

        final int wordIndex;
        if (paragraphLength == 0) {
            wordIndex = 0;
        } else {
            preparePaintInfo(myCurrentPage);
            final ZLTextWordCursor cursor = new ZLTextWordCursor(myCurrentPage.EndCursor);
            cursor.moveToParagraph(paragraphIndex);
            wordIndex = cursor.getParagraphCursor().getParagraphLength();
        }

        gotoPositionByEnd(paragraphIndex, wordIndex, 0);
    }

    private List<ZLTextHighlighting> findHilites(ZLTextPage page) {
        final LinkedList<ZLTextHighlighting> hilites = new LinkedList<ZLTextHighlighting>();
        if (mySelection == null || mySelection.intersects(page)) {
            hilites.add(mySelection);
        }
        synchronized (myHighlightings) {
            for (ZLTextHighlighting h : myHighlightings) {
                if (h.intersects(page)) {
                    hilites.add(h);
                }
            }
        }
        return hilites;
    }

    private void drawTextLine(ZLTextPage page, List<ZLTextHighlighting> hilites, ZLTextLineInfo info, int from, int to, int y) {
        final ZLPaintContext context = myView.getContext();
        //LogUtil.i(GlobalConstants.READER_LOG, "ModleData drawTextLine  切换下一行 = " + info.toString());
        if (myView.mTTSSelection != null) {
            myView.drawTTSSelectionBackground(myView.mTTSSelection, ZLTTSTextSelection.getSelectedColor(), page, info, from, to, y);
        }

        ZLTextSelection selection = getEditSelection();
        //LogUtil.e("TTS", "draw selection.. 1123");

        if (NotesData.instance().removeSelection(selection)) {
            LogUtil.e("TTS", "draw selection.. selection hashcode:" + selection.hashCode());
            myView.drawSelectionBackground(selection, myView.getSelectionBackgroundColor(), page, info, from, to, y);

        }

        FBReaderApp myReader = (FBReaderApp) FBReaderApp.Instance();
        if (myReader != null && myReader.Model != null && myReader.Model.Book != null && myReader.Model.Book instanceof M17kPlainTxtBook) {
//            M17kPlainTxtBook book = (M17kPlainTxtBook) myReader.Model.Book;
            Chapter readingChapter = (Chapter) myReadinChapter;
//                    book.getReader().getReadingChapter();
            if (readingChapter != null && readingChapter.getId() != null) {
                NotesData.instance().drawSelectionBackground(myView, page, info, from, to, y, readingChapter);
            }
        }


        final ZLTextParagraphCursor paragraph = info.ParagraphCursor;
        int index = from;
        final int endElementIndex = info.EndElementIndex;
        int charIndex = info.RealStartCharIndex;
        final List<ZLTextElementArea> pageAreas = page.TextElementMap.areas();
        if (to > pageAreas.size()) {
            return;
        }
        drawTextLineLog(info, paragraph, endElementIndex);

        for (int wordIndex = info.RealStartElementIndex; wordIndex != endElementIndex && index < to; ++wordIndex, charIndex = 0) {
            final ZLTextElement element = paragraph.getElement(wordIndex);
            final ZLTextElementArea area = pageAreas.get(index);
            if (element == area.Element) {
                ++index;
                if (area.ChangeStyle) {
                    myView.setTextStyle(area.Style);
                }
                final int areaX = area.XStart;
                final int areaY = area.YEnd - myView.getElementDescent(element) - myView.getTextStyle().getVerticalAlign(myView.metrics());
                if (element instanceof ZLTextWord) {
                    final ZLTextPosition pos =
                            new ZLTextFixedPosition(info.ParagraphCursor.Index, wordIndex, 0);
                    final ZLTextHighlighting hl = getWordHilite(pos, hilites);
                    final ZLColor hlColor = hl != null ? hl.getForegroundColor() : null;
                    myView.drawWord(
                            areaX, areaY, (ZLTextWord) element, charIndex, -1, false,
                            hlColor != null ? hlColor : myView.getTextColor(myView.getTextStyle().Hyperlink)
                    );
                } else if (element instanceof ZLTextImageElement) {
                    final ZLTextImageElement imageElement = (ZLTextImageElement) element;
                    context.drawImage(
                            areaX, areaY,
                            imageElement.ImageData,
                            myView.getTextAreaSize(),
                            myView.getScalingType(imageElement),
                            myView.getAdjustingModeForImages()
                    );
                } else if (element instanceof ZLTextVideoElement) {
                    // TODO: draw
                    context.setLineColor(myView.getTextColor(ZLTextHyperlink.NO_LINK));
                    context.setFillColor(new ZLColor(127, 127, 127));
                    final int xStart = area.XStart + 10;
                    final int xEnd = area.XEnd - 10;
                    final int yStart = area.YStart + 10;
                    final int yEnd = area.YEnd - 10;
                    context.fillRectangle(xStart, yStart, xEnd, yEnd);
                    context.drawLine(xStart, yStart, xStart, yEnd);
                    context.drawLine(xStart, yEnd, xEnd, yEnd);
                    context.drawLine(xEnd, yEnd, xEnd, yStart);
                    context.drawLine(xEnd, yStart, xStart, yStart);
                    final int l = xStart + (xEnd - xStart) * 7 / 16;
                    final int r = xStart + (xEnd - xStart) * 10 / 16;
                    final int t = yStart + (yEnd - yStart) * 2 / 6;
                    final int b = yStart + (yEnd - yStart) * 4 / 6;
                    final int c = yStart + (yEnd - yStart) / 2;
                    context.setFillColor(new ZLColor(196, 196, 196));
                    context.fillPolygon(new int[]{l, l, r}, new int[]{t, b, c});
                } else if (element instanceof ExtensionElement) {
                    ((ExtensionElement) element).draw(context, area);
                } else if (element == ZLTextElement.HSpace || element == ZLTextElement.NBSpace) {
                    final int cw = context.getSpaceWidth();
                    for (int len = 0; len < area.XEnd - area.XStart; len += cw) {
                        context.drawString(areaX + len, areaY, SPACE, 0, 1);
                    }
                } else if (element instanceof ZLTextHorizontalDividerLineElement) {
                    //绘制横线


                    //context.drawFilledCircle(areaX, areaY, 5);

                }
            }
        }
        if (index != to) {
            ZLTextElementArea area = pageAreas.get(index++);
            if (area.ChangeStyle) {
                myView.setTextStyle(area.Style);
            }
            final int start = info.StartElementIndex == info.EndElementIndex
                    ? info.StartCharIndex : 0;
            final int len = info.EndCharIndex - start;
            LogUtil.e("LOYY", "info:" + info + " info:");
            final ZLTextWord word = (ZLTextWord) paragraph.getElement(info.EndElementIndex);

            final ZLTextPosition pos =
                    new ZLTextFixedPosition(info.ParagraphCursor.Index, info.EndElementIndex, 0);
            final ZLTextHighlighting hl = getWordHilite(pos, hilites);
            final ZLColor hlColor = hl != null ? hl.getForegroundColor() : null;
            LogUtil.e(GlobalConstants.READER_LOG, "ModleData drawTextLine  word = " + String.valueOf(word.Data));
            myView.drawWord(area.XStart, area.YEnd - context.getDescent() - myView.getTextStyle().getVerticalAlign(myView.metrics()),
                    word, start, len, area.AddHyphenationSign,
                    hlColor != null ? hlColor : myView.getTextColor(myView.getTextStyle().Hyperlink)
            );
        }
    }

    private void drawTextLineLog(ZLTextLineInfo info, ZLTextParagraphCursor paragraph, int endElementIndex) {
        StringBuilder sb = new StringBuilder();
        for (int wordIndex = info.RealStartElementIndex; wordIndex < endElementIndex; wordIndex++) {
            final ZLTextElement element = paragraph.getElement(wordIndex);
            if (element instanceof ZLTextWord) {
                ZLTextWord word = (ZLTextWord) element;
                String ters = String.valueOf(word.Data);
                sb.append(ters.substring(word.Offset, word.Offset + word.Length));
            }
        }
        LogUtil.i(GlobalConstants.READER_LOG, "ModelData drawTextLine " + sb.toString());

    }

    private ZLTextHighlighting getWordHilite(ZLTextPosition pos, List<ZLTextHighlighting> hilites) {

        for (ZLTextHighlighting h : hilites) {
            if (h != null && h.getStartPosition() != null && h.getStartPosition().compareToIgnoreChar(pos) <= 0
                    && pos.compareToIgnoreChar(h.getEndPosition()) <= 0) {
                return h;
            }
        }
        return null;
    }

    /**
     * 处理章节内容
     *
     * @param page
     * @param start
     * @param result
     */
    private void buildInfos(ZLTextPage page, ZLTextWordCursor start, ZLTextWordCursor result) {
        //LogUtil.i(GlobalConstants.READER_LOG, "ModelData buildInfos 建立章节内容  start = " + start.getCharIndex() + " end = " + result.getCharIndex());
        result.setCursor(start);
        int textAreaHeight = page.getTextHeight();

        if (page.StartCursor.isStartOfText()) {
            textAreaHeight -= (int) AndroidUtils.dip2px(GlobalApp.getInstance(), 50);
        }

        page.LineInfos.clear();
        page.Column0Height = 0;
        boolean nextParagraph;
        ZLTextLineInfo info = null;
        do {
            final ZLTextLineInfo previousInfo = info;
            myView.resetTextStyle();
            final ZLTextParagraphCursor paragraphCursor = result.getParagraphCursor();
            final int wordIndex = result.getElementIndex();
            myView.applyStyleChanges(paragraphCursor, 0, wordIndex);
            info = new ZLTextLineInfo(paragraphCursor, wordIndex, result.getCharIndex(), myView.getTextStyle());
            final int endIndex = info.ParagraphCursorLength;
            while (info.EndElementIndex != endIndex) {
                info = processTextLine(page, paragraphCursor, info.EndElementIndex, info.EndCharIndex, endIndex, previousInfo);
                textAreaHeight -= info.Height + info.Descent;
                if (textAreaHeight < 0 && page.LineInfos.size() > page.Column0Height) {
                    if (page.Column0Height == 0 && page.twoColumnView()) {
                        textAreaHeight = page.getTextHeight();
                        textAreaHeight -= info.Height + info.Descent;
                        page.Column0Height = page.LineInfos.size();
                    } else {
                        break;
                    }
                }
                textAreaHeight -= info.VSpaceAfter;
                result.moveTo(info.EndElementIndex, info.EndCharIndex);
                page.LineInfos.add(info);
                if (textAreaHeight < 0) {
                    if (page.Column0Height == 0 && page.twoColumnView()) {
                        textAreaHeight = page.getTextHeight();
                        page.Column0Height = page.LineInfos.size();
                    } else {
                        break;
                    }
                }
            }
            nextParagraph = result.isEndOfParagraph() && result.nextParagraph();
            if (nextParagraph && result.getParagraphCursor().isEndOfSection()) {
                if (page.Column0Height == 0 && page.twoColumnView() && !page.LineInfos.isEmpty()) {
                    textAreaHeight = page.getTextHeight();
                    page.Column0Height = page.LineInfos.size();
                }
            }
        } while (nextParagraph && textAreaHeight >= 0 &&
                (!result.getParagraphCursor().isEndOfSection() ||
                        page.LineInfos.size() == page.Column0Height)
                );
        myView.resetTextStyle();
    }

    private boolean isHyphenationPossible() {
        return myView.getTextStyleCollection().getBaseStyle().AutoHyphenationOption.getValue()
                && myView.getTextStyle().allowHyphenations();
    }

    private volatile ZLTextWord myCachedWord;
    private volatile ZLTextHyphenationInfo myCachedInfo;

    private final synchronized ZLTextHyphenationInfo getHyphenationInfo(ZLTextWord word) {
        if (myCachedWord != word) {
            myCachedWord = word;
            myCachedInfo = ZLTextHyphenator.Instance().getInfo(word);
        }
        return myCachedInfo;
    }

    private ZLTextLineInfo processTextLine(
            ZLTextPage page,
            ZLTextParagraphCursor paragraphCursor,
            final int startIndex,
            final int startCharIndex,
            final int endIndex,
            ZLTextLineInfo previousInfo
    ) {
        final ZLTextLineInfo info = processTextLineInternal(
                page, paragraphCursor, startIndex, startCharIndex, endIndex, previousInfo
        );
        if (info.EndElementIndex == startIndex && info.EndCharIndex == startCharIndex) {
            info.EndElementIndex = paragraphCursor.getParagraphLength();
            info.EndCharIndex = 0;
            // TODO: add error element
        }
        return info;
    }

    private ZLTextLineInfo processTextLineInternal(
            ZLTextPage page,
            ZLTextParagraphCursor paragraphCursor,
            final int startIndex,
            final int startCharIndex,
            final int endIndex,
            ZLTextLineInfo previousInfo
    ) {
        final ZLPaintContext context = myView.getContext();
        final ZLTextLineInfo info = new ZLTextLineInfo(paragraphCursor, startIndex, startCharIndex, myView.getTextStyle());
        final ZLTextLineInfo cachedInfo = myLineInfoCache.get(info);
        //TODO：临时
        if (cachedInfo != null) {
            cachedInfo.adjust(previousInfo);
            myView.applyStyleChanges(paragraphCursor, startIndex, cachedInfo.EndElementIndex);
            return cachedInfo;
        }

        int currentElementIndex = startIndex;
        int currentCharIndex = startCharIndex;
        final boolean isFirstLine = startIndex == 0 && startCharIndex == 0;

        if (isFirstLine) {
            ZLTextElement element = paragraphCursor.getElement(currentElementIndex);
            while (myView.isStyleChangeElement(element)) {
                myView.applyStyleChangeElement(element);
                ++currentElementIndex;
                currentCharIndex = 0;
                if (currentElementIndex == endIndex) {
                    break;
                }
                element = paragraphCursor.getElement(currentElementIndex);
            }
            info.StartStyle = myView.getTextStyle();
            info.RealStartElementIndex = currentElementIndex;
            info.RealStartCharIndex = currentCharIndex;
        }

        ZLTextStyle storedStyle = myView.getTextStyle();

        final int maxWidth = page.getTextWidth() - storedStyle.getRightIndent(myView.metrics());
        info.LeftIndent = storedStyle.getLeftIndent(myView.metrics());
        if (isFirstLine && storedStyle.getAlignment() != ZLTextAlignmentType.ALIGN_CENTER) {
            info.LeftIndent += storedStyle.getFirstLineIndent(myView.metrics());
        }
        if (info.LeftIndent > maxWidth - 20) {
            info.LeftIndent = maxWidth * 3 / 4;
        }

        info.Width = info.LeftIndent;

        if (info.RealStartElementIndex == endIndex) {
            info.EndElementIndex = info.RealStartElementIndex;
            info.EndCharIndex = info.RealStartCharIndex;
            return info;
        }

        int newWidth = info.Width;
        int newHeight = info.Height;
        int newDescent = info.Descent;
        boolean wordOccurred = false;
        boolean isVisible = false;
        int lastSpaceWidth = 0;
        int internalSpaceCounter = 0;
        boolean removeLastSpace = false;

        do {
            ZLTextElement element = paragraphCursor.getElement(currentElementIndex);
            newWidth += myView.getElementWidth(element, currentCharIndex);
            newHeight = Math.max(newHeight, myView.getElementHeight(element));
            newDescent = Math.max(newDescent, myView.getElementDescent(element));
            if (element == ZLTextElement.HSpace) {
                if (wordOccurred) {
                    wordOccurred = false;
                    internalSpaceCounter++;
                    lastSpaceWidth = context.getSpaceWidth();
                    newWidth += lastSpaceWidth;
                }
            } else if (element == ZLTextElement.NBSpace) {
                wordOccurred = true;
            } else if (element instanceof ZLTextWord) {
                wordOccurred = true;
                isVisible = true;
            } else if (element instanceof ZLTextImageElement) {
                wordOccurred = true;
                isVisible = true;
            } else if (element instanceof ZLTextVideoElement) {
                wordOccurred = true;
                isVisible = true;
            } else if (element instanceof ExtensionElement) {
                wordOccurred = true;
                isVisible = true;
            } else if (element instanceof ZLTextHorizontalDividerLineElement) {
                wordOccurred = true;
                isVisible = true;
            } else if (myView.isStyleChangeElement(element)) {
                myView.applyStyleChangeElement(element);
            }
            if (newWidth > maxWidth) {
                if (info.EndElementIndex != startIndex || element instanceof ZLTextWord) {
                    break;
                }
            }
            ZLTextElement previousElement = element;
            ++currentElementIndex;
            currentCharIndex = 0;
            boolean allowBreak = currentElementIndex == endIndex;
            if (!allowBreak) {
                element = paragraphCursor.getElement(currentElementIndex);
                allowBreak =
                        previousElement != ZLTextElement.NBSpace &&
                                element != ZLTextElement.NBSpace &&
                                (!(element instanceof ZLTextWord) || previousElement instanceof ZLTextWord) &&
                                !(element instanceof ZLTextImageElement) &&
                                !(element instanceof ZLTextControlElement);
            }
            if (allowBreak) {
                info.IsVisible = isVisible;
                info.Width = newWidth;
                if (info.Height < newHeight) {
                    info.Height = newHeight;
                }
                if (info.Descent < newDescent) {
                    info.Descent = newDescent;
                }
                info.EndElementIndex = currentElementIndex;
                info.EndCharIndex = currentCharIndex;
                info.SpaceCounter = internalSpaceCounter;
                storedStyle = myView.getTextStyle();
                removeLastSpace = !wordOccurred && (internalSpaceCounter > 0);
            }
        } while (currentElementIndex != endIndex);

        if (currentElementIndex != endIndex &&
                (isHyphenationPossible() || info.EndElementIndex == startIndex)) {
            ZLTextElement element = paragraphCursor.getElement(currentElementIndex);
            if (element instanceof ZLTextWord) {
                final ZLTextWord word = (ZLTextWord) element;
                newWidth -= myView.getWordWidth(word, currentCharIndex);
                int spaceLeft = maxWidth - newWidth;
                if ((word.Length > 3 && spaceLeft > 2 * context.getSpaceWidth())
                        || info.EndElementIndex == startIndex) {
                    ZLTextHyphenationInfo hyphenationInfo = getHyphenationInfo(word);
                    int hyphenationPosition = currentCharIndex;
                    int subwordWidth = 0;
                    for (int right = word.Length - 1, left = currentCharIndex; right > left; ) {
                        final int mid = (right + left + 1) / 2;
                        int m1 = mid;
                        while (m1 > left && !hyphenationInfo.isHyphenationPossible(m1)) {
                            --m1;
                        }
                        if (m1 > left) {
                            final int w = myView.getWordWidth(
                                    word,
                                    currentCharIndex,
                                    m1 - currentCharIndex,
                                    word.Data[word.Offset + m1 - 1] != '-'
                            );
                            if (w < spaceLeft) {
                                left = mid;
                                hyphenationPosition = m1;
                                subwordWidth = w;
                            } else {
                                right = mid - 1;
                            }
                        } else {
                            left = mid;
                        }
                    }
                    if (hyphenationPosition == currentCharIndex && info.EndElementIndex == startIndex) {
                        subwordWidth = myView.getWordWidth(word, currentCharIndex, 1, false);
                        int right = word.Length == currentCharIndex + 1 ? word.Length : word.Length - 1;
                        int left = currentCharIndex + 1;
                        while (right > left) {
                            final int mid = (right + left + 1) / 2;
                            final int w = myView.getWordWidth(
                                    word,
                                    currentCharIndex,
                                    mid - currentCharIndex,
                                    word.Data[word.Offset + mid - 1] != '-'
                            );
                            if (w <= spaceLeft) {
                                left = mid;
                                subwordWidth = w;
                            } else {
                                right = mid - 1;
                            }
                        }
                        hyphenationPosition = right;
                    }
                    if (hyphenationPosition > currentCharIndex) {
                        info.IsVisible = true;
                        info.Width = newWidth + subwordWidth;
                        if (info.Height < newHeight) {
                            info.Height = newHeight;
                        }
                        if (info.Descent < newDescent) {
                            info.Descent = newDescent;
                        }
                        info.EndElementIndex = currentElementIndex;
                        info.EndCharIndex = hyphenationPosition;
                        info.SpaceCounter = internalSpaceCounter;
                        storedStyle = myView.getTextStyle();
                        removeLastSpace = false;
                    }
                }
            }
        }

        if (removeLastSpace) {
            info.Width -= lastSpaceWidth;
            info.SpaceCounter--;
        }

        myView.setTextStyle(storedStyle);

        if (isFirstLine) {
            info.VSpaceBefore = info.StartStyle.getSpaceBefore(myView.metrics());
            if (previousInfo != null) {
                info.PreviousInfoUsed = true;
                info.Height += Math.max(0, info.VSpaceBefore - previousInfo.VSpaceAfter);
            } else {
                info.PreviousInfoUsed = false;
                info.Height += info.VSpaceBefore;
            }
        }
        if (info.isEndOfParagraph()) {
            info.VSpaceAfter = myView.getTextStyle().getSpaceAfter(myView.metrics());
        }

        if (info.EndElementIndex != endIndex || endIndex == info.ParagraphCursorLength) {
            myLineInfoCache.put(info, info);
        }

        return info;
    }

    private void prepareTextLine(ZLTextPage page, ZLTextLineInfo info, int x, int y, int columnIndex) {
        y = Math.min(y + info.Height, myView.getTopMargin() + page.getTextHeight() - 1);
        final ZLPaintContext context = myView.getContext();
        final ZLTextParagraphCursor paragraphCursor = info.ParagraphCursor;

        myView.setTextStyle(info.StartStyle);
        int spaceCounter = info.SpaceCounter;
        int fullCorrection = 0;
        //段落尾行不用右对齐、其它行只要其包含的元素个数超过2个，都要右对齐
        final boolean endOfParagraph = info.isEndOfParagraph();
        boolean wordOccurred = false;
        boolean changeStyle = true;
        x += info.LeftIndent;


        final int maxWidth = page.getTextWidth();
        byte type = myView.getTextStyle().getAlignment();
        switch (type) {
            case ZLTextAlignmentType.ALIGN_RIGHT:
                x += maxWidth - myView.getTextStyle().getRightIndent(myView.metrics()) - info.Width;
                break;
            case ZLTextAlignmentType.ALIGN_CENTER:
                x += (maxWidth - myView.getTextStyle().getRightIndent(myView.metrics()) - info.Width) / 2;
                break;
            case ZLTextAlignmentType.ALIGN_JUSTIFY:
                if (!endOfParagraph && (paragraphCursor.getElement(info.EndElementIndex) != ZLTextElement.AfterParagraph)) {
                    fullCorrection = maxWidth - myView.getTextStyle().getRightIndent(myView.metrics()) - info.Width;
                }
                break;
            case ZLTextAlignmentType.ALIGN_LEFT:
            case ZLTextAlignmentType.ALIGN_UNDEFINED:
                break;
        }

        final ZLTextParagraphCursor paragraph = info.ParagraphCursor;
        final int paragraphIndex = paragraph.Index;
        final int endElementIndex = info.EndElementIndex;
        int charIndex = info.RealStartCharIndex;
        ZLTextElementArea spaceElement = null;
        int lineWidth = 0;
        int count = 0;
        for (int wordIndex = info.RealStartElementIndex; wordIndex != endElementIndex; ++wordIndex, charIndex = 0) {
            final ZLTextElement element = paragraph.getElement(wordIndex);
            lineWidth += myView.getElementWidth(element, charIndex);
            count++;
        }
        int rightX = myView.getContextWidth() - myView.getRightMargin();
        int avgrOffset = !endOfParagraph && count > 1 ? (myView.getContextWidth() - myView.getRightMargin() - x - lineWidth) / (count - 1) : 0;
        //LogUtil.e("DRPAINT", "endOfParagraph:" + endOfParagraph + " avgrOffset:" + avgrOffset);

        for (int wordIndex = info.RealStartElementIndex; wordIndex != endElementIndex; ++wordIndex, charIndex = 0) {
            final ZLTextElement element = paragraph.getElement(wordIndex);
            final int width = myView.getElementWidth(element, charIndex);
            boolean isFixed = !endOfParagraph && wordIndex + 1 >= endElementIndex;
            if (element == ZLTextElement.HSpace) {
                if (isFixed) {
                    LogUtil.e("DRPAINT", "最后一个字符");
                }

                if (wordOccurred && spaceCounter > 0) {
                    final int correction = fullCorrection / spaceCounter;
                    final int spaceLength = context.getSpaceWidth() + correction;
                    if (myView.getTextStyle().isUnderline()) {
                        spaceElement = new ZLTextElementArea(
                                paragraphIndex, wordIndex, 0,
                                0, // length
                                true, // is last in element
                                false, // add hyphenation sign
                                false, // changed style
                                myView.getTextStyle(), element, x, isFixed ? (rightX - spaceLength) : (x + spaceLength), y, y, columnIndex
                        );
                    } else {
                        spaceElement = null;
                    }
                    //x += spaceLength + avgrOffset;
                    fullCorrection -= correction;
                    wordOccurred = false;
                    --spaceCounter;
                }
            } else if (element instanceof ZLTextWord || element instanceof ZLTextImageElement || element instanceof ZLTextHorizontalDividerLineElement || element instanceof ZLTextVideoElement || element instanceof ExtensionElement) {

                if (isFixed) {
                    LogUtil.e("DRPAINT", "");
                }

                final int height = myView.getElementHeight(element);
                final int descent = myView.getElementDescent(element);
                final int length = element instanceof ZLTextWord ? ((ZLTextWord) element).Length : 0;
                if (spaceElement != null) {
                    page.TextElementMap.add(spaceElement);
                    spaceElement = null;
                }
                ZLTextElementArea area = new ZLTextElementArea(
                        paragraphIndex, wordIndex, charIndex,
                        length - charIndex,
                        true, // is last in element
                        false, // add hyphenation sign
                        changeStyle, myView.getTextStyle(), element,
                        isFixed ? (rightX - width) : x, isFixed ? (rightX - width) : (x + width - 1), y - height + 1, y + descent, columnIndex
                );
                area.ownerLine = info;
                page.TextElementMap.add(area);
                changeStyle = false;
                wordOccurred = true;
            } else if (myView.isStyleChangeElement(element)) {
                myView.applyStyleChangeElement(element);
                changeStyle = true;
            }
            x += width + avgrOffset;
        }
        if (!endOfParagraph) {
            final int len = info.EndCharIndex;
            if (len > 0) {
                final int wordIndex = info.EndElementIndex;
                final ZLTextWord word = (ZLTextWord) paragraph.getElement(wordIndex);
                final boolean addHyphenationSign = word.Data[word.Offset + len - 1] != '-';
                final int width = myView.getWordWidth(word, 0, len, addHyphenationSign);
                final int height = myView.getElementHeight(word);
                final int descent = context.getDescent();
                ZLTextElementArea area = new ZLTextElementArea(
                        paragraphIndex, wordIndex, 0,
                        len,
                        false, // is last in element
                        addHyphenationSign,
                        changeStyle, myView.getTextStyle(), word,
                        x, x + width - 1, y - height + 1, y + descent, columnIndex
                );
                area.ownerLine = info;
                page.TextElementMap.add(area);

            }
        }
    }

    protected synchronized void preparePaintInfo() {
        myPreviousPage.reset();
        myNextPage.reset();
        preparePaintInfo(myCurrentPage);
    }

    public final void gotoPosition(int paragraphIndex, int wordIndex, int charIndex) {
        gotoPosition(paragraphIndex, wordIndex, charIndex, true);
    }

    public final synchronized void gotoPosition(int paragraphIndex, int wordIndex, int charIndex, boolean scrollNextWhenEmptyPage) {
        if (myModel != null && myModel.getParagraphsNumber() > 0) {
            Application.getViewWidget().reset();
            myCurrentPage.moveStartCursor(paragraphIndex, wordIndex, charIndex);
            myPreviousPage.reset();
            myNextPage.reset();
            preparePaintInfo(myCurrentPage);
            //       LogUtil.e("Read", "myCurrentPage.isEmptyPage:" + myCurrentPage.isEmptyPage());
            ZLTextPosition startPosition = myCurrentPage.StartCursor;
            boolean flag = myCurrentPage.isEmptyPage();
            if (flag) {
                turnPage(scrollNextWhenEmptyPage, ZLTextView.ScrollingMode.NO_OVERLAPPING, 0);

            }
        }
    }

    public final synchronized void gotoPositionByEnd(int paragraphIndex, int wordIndex, int charIndex) {
        if (myModel != null && myModel.getParagraphsNumber() > 0) {
            myCurrentPage.moveEndCursor(paragraphIndex, wordIndex, charIndex);
            myPreviousPage.reset();
            myNextPage.reset();
            preparePaintInfo(myCurrentPage);
            if (myCurrentPage.isEmptyPage()) {
                turnPage(false, ZLTextView.ScrollingMode.NO_OVERLAPPING, 0);
            }
        }
    }

    private static class ParagraphSize {
        public int Height;
        public int TopMargin;
        public int BottomMargin;
    }

    private ParagraphSize paragraphSize(ZLTextPage page, ZLTextWordCursor cursor, boolean beforeCurrentPosition, int unit) {
        final ParagraphSize size = new ParagraphSize();

        final ZLTextParagraphCursor paragraphCursor = cursor.getParagraphCursor();
        if (paragraphCursor == null) {
            return size;
        }
        final int endElementIndex =
                beforeCurrentPosition ? cursor.getElementIndex() : paragraphCursor.getParagraphLength();

        myView.resetTextStyle();

        int wordIndex = 0;
        int charIndex = 0;
        ZLTextLineInfo info = null;
        while (wordIndex != endElementIndex) {
            final ZLTextLineInfo prev = info;
            info = processTextLine(page, paragraphCursor, wordIndex, charIndex, endElementIndex, prev);
            wordIndex = info.EndElementIndex;
            charIndex = info.EndCharIndex;
            size.Height += infoSize(info, unit);
            if (prev == null) {
                size.TopMargin = info.VSpaceBefore;
            }
            size.BottomMargin = info.VSpaceAfter;
        }

        return size;
    }

    private void skip(ZLTextPage page, ZLTextWordCursor cursor, int unit, int size) {
        final ZLTextParagraphCursor paragraphCursor = cursor.getParagraphCursor();
        if (paragraphCursor == null) {
            return;
        }
        final int endElementIndex = paragraphCursor.getParagraphLength();

        myView.resetTextStyle();
        myView.applyStyleChanges(paragraphCursor, 0, cursor.getElementIndex());

        ZLTextLineInfo info = null;
        while (!cursor.isEndOfParagraph() && size > 0) {
            info = processTextLine(page, paragraphCursor, cursor.getElementIndex(), cursor.getCharIndex(), endElementIndex, info);
            cursor.moveTo(info.EndElementIndex, info.EndCharIndex);
            size -= infoSize(info, unit);
        }
    }

    private ZLTextWordCursor findStartOfPrevousPage(ZLTextPage page, ZLTextWordCursor end) {
        if (myView.twoColumnView()) {
            end = findStart(page, end, SizeUnit.PIXEL_UNIT, page.getTextHeight());
        }
        end = findStart(page, end, SizeUnit.PIXEL_UNIT, page.getTextHeight());
        return end;
    }

    private ZLTextWordCursor findStart(ZLTextPage page, ZLTextWordCursor end, int unit, int height) {
        final ZLTextWordCursor start = new ZLTextWordCursor(end);
        ParagraphSize size = paragraphSize(page, start, true, unit);
        height -= size.Height;
        boolean positionChanged = !start.isStartOfParagraph();
        start.moveToParagraphStart();
        while (height > 0) {
            final ParagraphSize previousSize = size;
            if (positionChanged && start.getParagraphCursor().isEndOfSection()) {
                break;
            }
            if (!start.previousParagraph()) {
                break;
            }
            if (!start.getParagraphCursor().isEndOfSection()) {
                positionChanged = true;
            }
            size = paragraphSize(page, start, false, unit);
            height -= size.Height;
            if (previousSize != null) {
                height += Math.min(size.BottomMargin, previousSize.TopMargin);
            }
        }
        skip(page, start, unit, -height);

        if (unit == SizeUnit.PIXEL_UNIT) {
            boolean sameStart = start.samePositionAs(end);
            if (!sameStart && start.isEndOfParagraph() && end.isStartOfParagraph()) {
                ZLTextWordCursor startCopy = new ZLTextWordCursor(start);
                startCopy.nextParagraph();
                sameStart = startCopy.samePositionAs(end);
            }
            if (sameStart) {
                start.setCursor(findStart(page, end, SizeUnit.LINE_UNIT, 1));
            }
        }

        return start;
    }


    private void scrollToBackward(ZLTextPage page) {
        if (!page.StartCursor.isStartOfText()) {
            switch (myScrollingMode) {
                case ZLTextView.ScrollingMode.NO_OVERLAPPING:
                    page.StartCursor.setCursor(findStartOfPrevousPage(page, page.StartCursor));
                    break;
                case ZLTextView.ScrollingMode.KEEP_LINES: {
                    ZLTextWordCursor endCursor = new ZLTextWordCursor();
                    page.findLineFromStart(endCursor, myOverlappingValue);
                    if (!endCursor.isNull() && endCursor.samePositionAs(page.EndCursor)) {
                        page.findLineFromEnd(endCursor, 1);
                    }
                    if (!endCursor.isNull()) {
                        ZLTextWordCursor startCursor = findStartOfPrevousPage(page, endCursor);
                        if (startCursor.samePositionAs(page.StartCursor)) {
                            page.StartCursor.setCursor(findStartOfPrevousPage(page, page.StartCursor));
                        } else {
                            page.StartCursor.setCursor(startCursor);
                        }
                    } else {
                        page.StartCursor.setCursor(findStartOfPrevousPage(page, page.StartCursor));
                    }
                    break;
                }
                case ZLTextView.ScrollingMode.SCROLL_LINES:
                    page.StartCursor.setCursor(findStart(page, page.StartCursor, SizeUnit.LINE_UNIT, myOverlappingValue));
                    break;
                case ZLTextView.ScrollingMode.SCROLL_PERCENTAGE:
                    page.StartCursor.setCursor(findStart(page, page.StartCursor, SizeUnit.PIXEL_UNIT, page.getTextHeight() * myOverlappingValue / 100));
                    break;
            }
            buildInfos(page, page.StartCursor, page.EndCursor);
            if (page.isEmptyPage()) {
                page.StartCursor.setCursor(findStart(page, page.StartCursor, SizeUnit.LINE_UNIT, 1));
                buildInfos(page, page.StartCursor, page.EndCursor);
            }
        }
    }

    public final void turnPage(boolean forward, int scrollingMode, int value) {
        synchronized (AnimationManager.mLockObj) {
            preparePaintInfo(myCurrentPage);
            myPreviousPage.reset();
            myNextPage.reset();
            if (myCurrentPage.PaintState == PaintStateEnum.READY) {
                myCurrentPage.PaintState = forward ? PaintStateEnum.TO_SCROLL_FORWARD : PaintStateEnum.TO_SCROLL_BACKWARD;
                myScrollingMode = scrollingMode;
                myOverlappingValue = value;
                preparePaintInfo(myCurrentPage);
                myPreviousPage.reset();
                myNextPage.reset();
            }
        }


    }

    protected void rebuildPaintInfo() {
        myPreviousPage.reset();
        myNextPage.reset();
        if (myCursorManager != null) {
            myCursorManager.evictAll();
        }

        if (myCurrentPage.PaintState != PaintStateEnum.NOTHING_TO_PAINT) {
            myCurrentPage.LineInfos.clear();
            if (!myCurrentPage.StartCursor.isNull()) {
                myCurrentPage.StartCursor.rebuild();
                myCurrentPage.EndCursor.reset();
                myCurrentPage.PaintState = PaintStateEnum.START_IS_KNOWN;
            } else if (!myCurrentPage.EndCursor.isNull()) {
                myCurrentPage.EndCursor.rebuild();
                myCurrentPage.StartCursor.reset();
                myCurrentPage.PaintState = PaintStateEnum.END_IS_KNOWN;
            }
        }

        myLineInfoCache.clear();
    }

    private int infoSize(ZLTextLineInfo info, int unit) {
        return (unit == SizeUnit.PIXEL_UNIT) ? (info.Height + info.Descent + info.VSpaceAfter) : (info.IsVisible ? 1 : 0);
    }


    protected ZLTextElementArea getElementByCoordinates(int x, int y) {
        synchronized (AnimationManager.mLockObj) {
            return myCurrentPage.TextElementMap.binarySearch(x, y);
        }
    }

    public final void outlineRegion(ZLTextRegion region) {
        outlineRegion(region != null ? region.getSoul() : null);
    }

    public final void outlineRegion(ZLTextRegion.Soul soul) {
        myShowOutline = true;
        myOutlinedRegionSoul = soul;
    }

    public void hideOutline() {
        myShowOutline = false;
        Application.getViewWidget().reset();
    }

    private ZLTextRegion getOutlinedRegion(ZLTextPage page) {
        synchronized (AnimationManager.mLockObj) {
            return page.TextElementMap.getRegion(myOutlinedRegionSoul);
        }
    }

    public ZLTextRegion getOutlinedRegion() {
        return getOutlinedRegion(myCurrentPage);
    }


    protected ZLTextRegion findRegion(int x, int y, int maxDistance, ZLTextRegion.Filter filter) {
        synchronized (AnimationManager.mLockObj) {
            return myCurrentPage.TextElementMap.findRegion(x, y, maxDistance, filter);
        }
    }

    protected ZLTextElementAreaVector.RegionPair findRegionsPair(int x, int y, ZLTextRegion.Filter filter) {
        synchronized (AnimationManager.mLockObj) {
            return myCurrentPage.TextElementMap.findRegionsPair(x, y, myView.getColumnIndex(x), filter);
        }
    }


    public boolean isSelectionEmpty() {
        return mySelection == null || mySelection.isEmpty();
    }

    public ZLTextRegion nextRegion(ZLViewEnums.Direction direction, ZLTextRegion.Filter filter) {
        synchronized (AnimationManager.mLockObj) {
            return myCurrentPage.TextElementMap.nextRegion(getOutlinedRegion(), direction, filter);
        }
    }

    protected ZLTextHighlighting findHighlighting(int x, int y, int maxDistance) {
        synchronized (AnimationManager.mLockObj) {

            final ZLTextRegion region = findRegion(x, y, maxDistance, ZLTextRegion.AnyRegionFilter);
            if (region == null) {
                return null;
            }
            synchronized (myHighlightings) {
                for (ZLTextHighlighting h : myHighlightings) {
                    if (h.getBackgroundColor() != null && h.intersects(region)) {
                        return h;
                    }
                }
            }
            return null;
        }
    }

    protected ZLTextRegion findRegion(int x, int y, ZLTextRegion.Filter filter) {
        return findRegion(x, y, Integer.MAX_VALUE - 1, filter);
    }

    /**
     * Thread safe
     *
     * @param index
     * @return
     */
    public boolean canScroll(ZLViewEnums.PageIndex index) {
        switch (index) {
            default:
                return true;
            case next: {
                final ZLTextWordCursor cursor = getEndCursor();
                return cursor != null && !cursor.isNull() && !cursor.isEndOfText();
            }
            case previous: {
                final ZLTextWordCursor cursor = getStartCursor();
                return cursor != null && !cursor.isNull() && !cursor.isStartOfText();
            }
        }
    }

    ZLTextParagraphCursor cursor(int index) {
        try {
            return myCursorManager.get(index);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public ZLTextSelection getEditSelection() {
        return mySelection != null && !mySelection.isEmpty() ? mySelection : null;
    }

    public void doAddNewNote(BookReadNote note) {
        if (NotesData.instance().doAddNewNote(myView, note)) {
            Application.getViewWidget().repaintForcely();
        }
    }

    public void doAddNewNote(LocalDumpNote note) {
        if (NotesData.instance().doAddNewNote(myView, note)) {
            Application.getViewWidget().repaintForcely();
        }
    }

    public void doUpdateNewNote(BookReadNote note) {
        if (NotesData.instance().doUpdateNewNote(myView, note)) {
            Application.getViewWidget().repaintForcely();
        }
    }

    public void doDeleteNewNote(BookReadNote note) {
        if (NotesData.instance().doDeleteNewNote(note)) {
            Application.getViewWidget().repaintForcely();
        }
    }

    public void loadZLSelectionData(List<BookReadNote> notes, List<LocalDumpNote> localNotes) {
        NotesData.instance().loadZLSelectionData(myView, notes, localNotes);
    }

    public ZLTextRegion getSelectedRegion() {
        return getSelectedRegion(myCurrentPage);
    }

    public ZLTextRegion getSelectedRegion(ZLTextPage page) {
        return page.TextElementMap.getRegion(mySelectedRegionSoul);
    }

    public void selectRegion(ZLTextRegion region) {
        final ZLTextRegion.Soul soul = region != null ? region.getSoul() : null;
        if (soul == null || (mySelectedRegionSoul != null && !soul.equals(mySelectedRegionSoul))) {
            myHighlightSelectedRegion = true;
        }
        mySelectedRegionSoul = soul;
    }


    protected boolean isSelectStartPoint(int x, int y) {
        if (mySelection != null && !mySelection.isEmpty()) {
            ZLTextSelection.Point leftPoint = getSelectionCursorPoint(
                    myCurrentPage, SelectionCursor.Which.Left);
            ZLTextSelection.Point rightPoint = getSelectionCursorPoint(
                    myCurrentPage, SelectionCursor.Which.Right);
            int rh = myView.getWordHeight() + 8 + 60;
            int endY = 0;
            int endX = 0;
            if (leftPoint != null) {
                endY = leftPoint.Y;
                endX = leftPoint.X;
            }
            if (rightPoint != null) {
                if (leftPoint == null || leftPoint.Y > rightPoint.Y) {
                    endY = rightPoint.Y;
                    endX = rightPoint.X;
                }
            }

            return (Math.abs(endX - x) <= 60 && Math.abs((endY - myView.getWordHeight() / 2) - y) <= rh / 2);
        }

        return false;
    }

    protected boolean isSelectEndPoint(float x, float y) {
        if (mySelection != null && !mySelection.isEmpty()) {
            ZLTextSelection.Point leftPoint = getSelectionCursorPoint(
                    myCurrentPage, SelectionCursor.Which.Left);
            ZLTextSelection.Point rightPoint = getSelectionCursorPoint(
                    myCurrentPage, SelectionCursor.Which.Right);
            if (leftPoint == null || rightPoint == null) {
                return false;
            }
            int endY = rightPoint.Y;
            int endX = rightPoint.X;
            if (leftPoint.Y > rightPoint.Y) {
                endY = leftPoint.Y;
                endX = leftPoint.X;
            }
            int rh = myView.getWordHeight() + 8 + 60;
            return (Math.abs(endX - x) <= 60 && Math
                    .abs((endY - myView.getWordHeight() / 2) - y) <= rh / 2);
        }

        return false;
    }

    protected boolean initSelection(float x, float y) {
        y -= ZLTextSelectionCursor.getHeight() / 2
                + ZLTextSelectionCursor.getAccent() / 2;
        mySelection = new ZLTextSelection(myView);
        return mySelection.start((int) x, (int) y);
    }

//    public boolean initSelection(ZLTextRegion startSelection,
//                                 ZLTextRegion endSelection) {
//
//         mySelection.clear();
//        if (!mySelection.start(startSelection, endSelection)) {
//            return false;
//        }
//        Application.getViewWidget().reset();
//        Application.getViewWidget().repaint();
//        return true;
//    }

    public void clearSelection() {
        if (null == mySelection) {
            return;
        }
        mySelection.clear();
        Application.getViewWidget().repaintForcely();
    }

    public ZLTextHighlighting getSelectionHighlighting() {
        return mySelection;
    }

    public Pair<Integer, Integer> getSelectionEndXY(ZLTextSelection selection) {
        if (selection == null || selection.isEmpty()) {
            return Pair.create(-100, -100);
        }

        if (selection.getNoteData() != null && selection.getNoteData() instanceof BookReadNote) {
            ZLTextElementArea endArea = selection.getNoteEndArea(myCurrentPage);
            if (endArea != null) {
                return Pair.create(endArea.XEnd, endArea.YEnd);
            }
            return Pair.create(-100, -100);
        } else {
            ZLTextElementArea endArea = selection.getStartArea(myCurrentPage);
            if (endArea != null) {
                return Pair.create(endArea.XEnd, endArea.YEnd);
            }
            if (selection.hasPartBeforePage(myCurrentPage)) {
                endArea = myCurrentPage.TextElementMap.getFirstArea();
                if (endArea != null) {
                    return Pair.create(endArea.XEnd, endArea.YEnd);
                }
            } else {
                endArea = myCurrentPage.TextElementMap.getLastArea();
                if (endArea != null) {
                    return Pair.create(endArea.XEnd, endArea.YEnd);
                }
            }
        }
        return Pair.create(-100, -100);
    }

    public int getSelectionStartY() {
        return getSelectionStartY(mySelection);
    }

    public int getSelectionStartY(ZLTextSelection selection) {
        if (selection == null || selection.isEmpty()) {
            return 0;
        }
        final ZLTextElementArea selectionStartArea = selection.getStartArea(myCurrentPage);
        if (selectionStartArea != null) {
            return selectionStartArea.YStart;
        }
        if (selection.hasPartBeforePage(myCurrentPage)) {
            final ZLTextElementArea firstArea = myCurrentPage.TextElementMap.getFirstArea();
            return firstArea != null ? firstArea.YStart : 0;
        } else {
            final ZLTextElementArea lastArea = myCurrentPage.TextElementMap.getLastArea();
            return lastArea != null ? lastArea.YEnd : 0;
        }
    }

    public int getSelectionEndY(ZLTextSelection selection) {
        if (selection == null || selection.isEmpty()) {
            return 0;
        }
        final ZLTextElementArea selectionEndArea = selection.getEndArea(myCurrentPage);
        if (selectionEndArea != null) {
            return selectionEndArea.YEnd;
        }
        if (selection.hasPartAfterPage(myCurrentPage)) {
            final ZLTextElementArea lastArea = myCurrentPage.TextElementMap.getLastArea();
            return lastArea != null ? lastArea.YEnd : 0;
        } else {
            final ZLTextElementArea firstArea = myCurrentPage.TextElementMap.getFirstArea();
            return firstArea != null ? firstArea.YStart : 0;
        }
    }

    public ZLTextPosition getSelectionStartPosition() {
        return mySelection == null ? null : mySelection.getStartPosition();
    }

    public ZLTextPosition getSelectionEndPosition() {
        return mySelection == null ? null : mySelection.getEndPosition();
    }

    public int getSelectionStartX() {
        return getSelectionStartX(mySelection);
    }

    public int getSelectionStartX(ZLTextSelection selection) {
        if (selection == null || selection.isEmpty()) {
            return 0;
        }
        final ZLTextElementArea selectionStartArea = selection
                .getStartArea(myCurrentPage);
        if (selectionStartArea != null) {
            return selectionStartArea.XStart;
        }
        if (selection.hasPartBeforePage(myCurrentPage)) {
            final ZLTextElementArea firstArea = myCurrentPage.TextElementMap
                    .getFirstArea();
            return firstArea != null ? firstArea.XStart : 0;
        } else {
            final ZLTextElementArea lastArea = myCurrentPage.TextElementMap
                    .getFirstArea();
            return lastArea != null ? lastArea.XStart : 0;
        }
    }


    public int getSelectionEndX(ZLTextSelection selection) {
        if (selection == null || selection.isEmpty()) {
            return 0;
        }
        final ZLTextElementArea selectionEndArea = selection
                .getEndArea(myCurrentPage);
        if (selectionEndArea != null) {
            return selectionEndArea.XEnd;
        }
        if (selection.hasPartAfterPage(myCurrentPage)) {
            final ZLTextElementArea lastArea = myCurrentPage.TextElementMap
                    .getLastArea();
            return lastArea != null ? lastArea.XEnd : 0;
        } else {
            final ZLTextElementArea firstArea = myCurrentPage.TextElementMap
                    .getFirstArea();
            return firstArea != null ? firstArea.XStart : 0;
        }
    }

    public boolean initSelection(ZLTextRegion startSelection,
                                 ZLTextRegion endSelection) {


        mySelection = new ZLTextSelection(myView);
        return mySelection.start(startSelection, endSelection);
    }

    public synchronized void clearCaches() {
        myCurrentPage.reset();
        myPreviousPage.reset();
        myNextPage.reset();
        myCharWidth = -1;

    }

    public synchronized void clear() {
        myModel = null;
        myCurrentPage.reset();
        myCurrentPage.setModel(myModel);
        myPreviousPage.reset();
        myPreviousPage.setModel(myModel);
        myNextPage.reset();
        myNextPage.setModel(myModel);
        myCharWidth = -1;
    }

    public int getElementCount() {
        return mTotalElementSize;
    }

    public int getReadElementId(ZLViewEnums.PageIndex index) {
        int readElementId = 0;
        ZLTextParagraphCursor cursor = null;
        if (myModel != null) {
            final int paragraphsNumber = myModel.getParagraphsNumber();
            int id = 0;

            readElementId += getStartCursor(index).getElementIndex();
            cursor = getStartCursor(index).getParagraphCursor();
            while (cursor != null) {
                readElementId += cursor.getParagraphLength();
                cursor = cursor.previous();
            }
        }
        return readElementId;
    }

    public int[] parseParaghAndElementId(int totalElementId) {
        if (myModel != null) {
            final int paragraphsNumber = myModel.getParagraphsNumber();
            int id = 0;
            int count = 0;
            int paragraphId = 0;

            ZLTextParagraphCursor cursor = paragraphsNumber > 0 ? myCursorManager.get(0) : null;
            while (cursor != null && (count + cursor.getParagraphLength() < totalElementId)) {
                paragraphId++;
                count += cursor.getParagraphLength();
                cursor = cursor.next();
            }
            return new int[]{paragraphId, totalElementId - count};
        }
        return null;
    }
}